/*
 * Copyright (C) 2006-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

package org.alfresco.jlan.netbios.server;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.netbios.NetBIOSName;
import org.alfresco.jlan.netbios.NetBIOSNameList;
import org.alfresco.jlan.netbios.NetBIOSPacket;
import org.alfresco.jlan.netbios.NetworkSettings;
import org.alfresco.jlan.netbios.RFCNetBIOSProtocol;
import org.alfresco.jlan.server.NetworkServer;
import org.alfresco.jlan.server.ServerListener;
import org.alfresco.jlan.server.Version;
import org.alfresco.jlan.server.config.ConfigId;
import org.alfresco.jlan.server.config.ConfigurationListener;
import org.alfresco.jlan.server.config.InvalidConfigurationException;
import org.alfresco.jlan.server.config.ServerConfiguration;
import org.alfresco.jlan.smb.server.CIFSConfigSection;
import org.alfresco.jlan.util.HexDump;
import org.alfresco.jlan.util.StringList;

/**
 * NetBIOS name server class.
 *
 * @author gkspencer
 */
public class NetBIOSNameServer extends NetworkServer implements Runnable, ConfigurationListener {

	//	Server version

	private static final String ServerVersion = Version.NetBIOSServerVersion;

  //	Various NetBIOS packet sizes

  public static final int AddNameSize 	 	= 256;
	public static final int DeleteNameSize 	= 256;
	public static final int RefreshNameSize	= 256;

  //	Add name thread broadcast interval and retry count

  private static final int AddNameInterval 			= 2000; // ms between transmits
  private static final int AddNameRetries 			= 5; 		// number of broadcasts

  private static final int AddNameWINSInterval	= 250;	//	ms between requests when using WINS

	//	Delete name interval and retry count

	private static final int DeleteNameInterval 	= 200;	// ms between transmits
	private static final int DeleteNameRetries 		= 1;		// number of broadcasts

	//	Refresh name retry count

	public static final int RefreshNameRetries		= 2;		// number of broadcasts

  // NetBIOS flags

  public static final int GroupName = 0x8000;

	//	Default time to live value for names registered by this server, in seconds

	public static final int DefaultTTL	=	10800;	// 3 hours

	//	Name refresh thread wakeup interval

	public static final long NameRefreshWakeupInterval	= 180000L;	//	3 minutes

  //  CIFS server configuration section

  private CIFSConfigSection m_cifsConfig;

  //	Name transaction id

  private static int m_tranId;

  //	NetBIOS name service datagram socket

  private DatagramSocket m_socket;

  //	Shutdown flag

  private boolean m_shutdown;

  //	Local address to bind the name server to

  private InetAddress m_bindAddress;

	//	Broadcast address, if not using WINS

	private InetAddress m_bcastAddr;

  //	Port/socket to bind to

  private int m_port = RFCNetBIOSProtocol.NAME_PORT;

	//	WINS server addresses

	private InetAddress m_winsPrimary;
	private InetAddress m_winsSecondary;

  //	Local add name listener list

  private Vector<AddNameListener> m_addListeners;

  //	Local name query listener list

  private Vector<QueryNameListener> m_queryListeners;

  //	Remote name add listener list

  private Vector<RemoteNameListener> m_remoteListeners;

  //	Local NetBIOS name table

  private Vector<NetBIOSName> m_localNames;

  //	Remote NetBIOS name table

  private Hashtable<NetBIOSName, byte[]> m_remoteNames;

	//	List of active add name requests

	private Vector<NetBIOSRequest> m_reqList;

	//	NetBIOS request handler and name refresh threads

	private NetBIOSRequestHandler m_reqHandler;
	private NetBIOSNameRefresh m_refreshThread;

	//	Server thread

	private Thread m_srvThread;

	//	NetBIOS request handler thread inner class

	class NetBIOSRequestHandler extends Thread {

		//	Shutdown request flag

		private boolean m_hshutdown = false;

		/**
		 * Default constructor
		 */
		public NetBIOSRequestHandler() {
	    setDaemon(true);
	    setName("NetBIOSRequest");
		}

		/**
		 * Shutdown the request handler thread
		 */
		public final void shutdownRequest() {
			m_hshutdown = true;

			synchronized ( m_reqList) {
				m_reqList.notify();
			}
		}

		/**
		 * Main thread code
		 */
		public void run() {

			//	Loop until shutdown requested

			while ( m_hshutdown == false) {

				try {

					//	Wait for something to do

					NetBIOSRequest req = null;

					synchronized ( m_reqList) {

						//	Check if there are any requests in the queue

						if ( m_reqList.size() == 0) {

							//	Debug

							if ( Debug.EnableInfo && hasDebug())
								Debug.println("NetBIOS handler waiting for request ...");

							//	Wait for some work ...

							m_reqList.wait();
						}

						//	Remove a request from the queue

						if ( m_reqList.size() > 0)
							req = m_reqList.elementAt(0);
						else if ( m_hshutdown == true)
							break;
					}

					//	Get the request retry count, for WINS only send one request

					int reqRetry = req.getRetryCount();
					if ( hasPrimaryWINSServer())
						reqRetry = 1;

					//	Process the request

					boolean txsts = true;
					int retry = 0;

					while ( req.hasErrorStatus() == false && retry++ < reqRetry) {

						//	Debug

						if (Debug.EnableInfo && hasDebug())
							Debug.println("NetBIOS handler, processing " + req);

						//	Process the request

						switch( req.isType()) {

							//	Add name request

							case NetBIOSRequest.AddName:

								//	Check if a WINS server is configured

								if ( hasPrimaryWINSServer())
									txsts = sendAddName(req, getPrimaryWINSServer(), false);
								else
									txsts = sendAddName(req, getBroadcastAddress(), true);
								break;

							//	Delete name request

							case NetBIOSRequest.DeleteName:

								//	Check if a WINS server is configured

								if ( hasPrimaryWINSServer())
									txsts = sendDeleteName(req, getPrimaryWINSServer(), false);
								else
									txsts = sendDeleteName(req, getBroadcastAddress(), true);
								break;

							//	Refresh name request

							case NetBIOSRequest.RefreshName:

								//	Check if a WINS server is configured

								if ( hasPrimaryWINSServer())
									txsts = sendRefreshName(req, getPrimaryWINSServer(), false);
								else
									txsts = sendRefreshName(req, getBroadcastAddress(), true);
								break;
						}

						//	Check if the request was successful

						if ( txsts == true && req.getRetryInterval() > 0) {

							//	Sleep for a while

							sleep(req.getRetryInterval());
						}
					}

					//	Check if the request was successful

					if ( req.hasErrorStatus() == false) {

						//	Debug

						if (Debug.EnableInfo && hasDebug())
							Debug.println("NetBIOS handler successful, " + req);

						//	Update the name record

						NetBIOSName nbName = req.getNetBIOSName();

						switch ( req.isType()) {

							//	Add name request

							case NetBIOSRequest.AddName:

								//	Add the name to the list of local names

								if ( m_localNames.contains(nbName) == false)
									m_localNames.add(nbName);

								//	Update the expiry time for the name

								nbName.setExpiryTime(System.currentTimeMillis() + ( nbName.getTimeToLive() * 1000L));

								//	Inform listeners that the request was successful

								fireAddNameEvent(nbName, NetBIOSNameEvent.ADD_SUCCESS);
								break;

							//	Delete name request

							case NetBIOSRequest.DeleteName:

								//	Remove the name from the list of local names

								m_localNames.removeElement(req.getNetBIOSName());
								break;

							//	Refresh name registration request

							case NetBIOSRequest.RefreshName:

								//	Update the expiry time for the name

								nbName.setExpiryTime(System.currentTimeMillis() + ( nbName.getTimeToLive() * 1000L));
								break;
						}
					}
					else {

						//	Error occurred

						switch( req.isType()) {

							//	Add name request

							case NetBIOSRequest.AddName:

								//	Remove the name from the local name list

								m_localNames.removeElement(req.getNetBIOSName());
								break;
						}
					}

					//	Remove the request from the queue

					synchronized( m_reqList) {
						m_reqList.removeElementAt(0);
					}
				}
				catch (InterruptedException ex) {
				}

				//	Check if the request handler has been shutdown

				if ( m_hshutdown == true)
					break;
			}
		}

		/**
		 * Send an add name request
		 *
		 * @param req NetBIOSRequest
		 * @param dest InetAddress
		 * @param bcast boolean
		 * @return boolean
		 */
		private final boolean sendAddName(NetBIOSRequest req, InetAddress dest, boolean bcast) {

			try {

				//  Allocate a buffer for the add name NetBIOS packet

				byte[] buf = new byte[AddNameSize];
				NetBIOSPacket addPkt = new NetBIOSPacket(buf);

				//  Build an add name packet for each IP address

				for ( int i = 0; i < req.getNetBIOSName().numberOfAddresses(); i++) {

					//	Build an add name request for the current IP address

					int len = addPkt.buildAddNameRequest(req.getNetBIOSName(), i, req.getTransactionId());
					if ( bcast == false)
						addPkt.setFlags(0);

					//  Allocate the datagram packet, using the add name buffer

					DatagramPacket pkt = new DatagramPacket(buf, len, dest, getPort());

					//	Send the add name request

					if ( m_socket != null)
						m_socket.send(pkt);

					//	Debug

					if (Debug.EnableInfo && hasDebug())
						Debug.println("  Add name " + ( bcast ? "broadcast" : "WINS") + ", " + req);
				}
			}
			catch (IOException ex) {
				fireAddNameEvent(req.getNetBIOSName(), NetBIOSNameEvent.ADD_IOERROR);
				req.setErrorStatus(true);
				return false;
			}

			//	Add name broadcast successful

			return true;
		}

		/**
		 * Send a refresh name request
		 *
		 * @param req NetBIOSRequest
		 * @param dest InetAddress
		 * @param bcast boolean
		 * @return boolean
		 */
		private final boolean sendRefreshName(NetBIOSRequest req, InetAddress dest, boolean bcast) {

			try {

				//  Allocate a buffer for the refresh name NetBIOS packet

				byte[] buf = new byte[RefreshNameSize];
				NetBIOSPacket refreshPkt = new NetBIOSPacket(buf);

				//  Build a refresh name packet for each IP address

				for ( int i = 0; i < req.getNetBIOSName().numberOfAddresses(); i++) {

					//	Build a refresh name request for the current IP address

					int len = refreshPkt.buildRefreshNameRequest(req.getNetBIOSName(), i, req.getTransactionId());
					if ( bcast == false)
						refreshPkt.setFlags(0);

					//  Allocate the datagram packet, using the refresh name buffer

					DatagramPacket pkt = new DatagramPacket(buf, len, dest, getPort());

					//	Send the refresh name request

					if ( m_socket != null)
						m_socket.send(pkt);

					//	Debug

					if (Debug.EnableInfo && hasDebug())
						Debug.println("  Refresh name " + ( bcast ? "broadcast" : "WINS") + ", " + req);
				}
			}
			catch (IOException ex) {
				req.setErrorStatus(true);
				return false;
			}

			//	Add name broadcast successful

			return true;
		}

		/**
		 * Send a delete name request via a network broadcast
		 *
		 * @param req NetBIOSRequest
		 * @param dest InetAddress
		 * @param bcast boolean
		 * @return boolean
		 */
		private final boolean sendDeleteName(NetBIOSRequest req, InetAddress dest, boolean bcast) {

			try {

				//  Allocate a buffer for the delete name NetBIOS packet

				byte[] buf = new byte[DeleteNameSize];
				NetBIOSPacket delPkt = new NetBIOSPacket(buf);

				//  Build a delete name packet for each IP address

				for ( int i = 0; i < req.getNetBIOSName().numberOfAddresses(); i++) {

					//	Build an add name request for the current IP address

					int len = delPkt.buildDeleteNameRequest(req.getNetBIOSName(), i, req.getTransactionId());
					if ( bcast == false)
						delPkt.setFlags(0);

					//  Allocate the datagram packet, using the add name buffer

					DatagramPacket pkt = new DatagramPacket(buf, len, dest, getPort());

					//	Send the add name request

					if ( m_socket != null)
						m_socket.send(pkt);

					//	Debug

					if (Debug.EnableInfo && hasDebug())
						Debug.println("  Delete name " + ( bcast ? "broadcast" : "WINS") + ", " + req);
				}
			}
			catch (IOException ex) {
				req.setErrorStatus(true);
				return false;
			}

			//	Delete name broadcast successful

			return true;
		}
	};

	//	NetBIOS name refresh thread inner class

	class NetBIOSNameRefresh extends Thread {

		//	Shutdown request flag

		private boolean m_hshutdown = false;

		/**
		 * Default constructor
		 */
		public NetBIOSNameRefresh() {
			setDaemon(true);
			setName("NetBIOSRefresh");
		}

		/**
		 * Shutdown the name refresh thread
		 */
		public final void shutdownRequest() {
			m_hshutdown = true;

			//	Wakeup the thread

			this.interrupt();
		}

		/**
		 * Main thread code
		 */
		public void run() {

			//	Loop for ever

			while ( m_hshutdown == false) {

				try {

					//	Sleep for a while

					sleep(NameRefreshWakeupInterval);

					//	Check if there is a shutdown pending

					if ( m_hshutdown == true)
						break;

					//	Debug

					if ( Debug.EnableInfo && hasDebug())
						Debug.println("NetBIOS name refresh wakeup ...");

					//	Check if there are any registered names that will expire in the next interval

					synchronized ( m_localNames) {

						//	Get the current time plus the wakeup interval

						long expireTime = System.currentTimeMillis() + NameRefreshWakeupInterval;

						//	Loop through the local name list

						for ( int i = 0; i < m_localNames.size(); i++) {

							//	Get a name from the list

							NetBIOSName nbName = (NetBIOSName) m_localNames.elementAt(i);

							//	Check if the name has expired, or will expire before the next wakeup event

							if ( nbName.getExpiryTime() < expireTime) {

								//	Debug

								if ( Debug.EnableInfo && hasDebug())
									Debug.println("Queuing name refresh for " + nbName);

								//	Queue a refresh request for the NetBIOS name

								NetBIOSRequest nbReq = new NetBIOSRequest(NetBIOSRequest.RefreshName, nbName, getNextTransactionId());
								nbReq.setRetryCount(RefreshNameRetries);

								//	Queue the request

								synchronized (m_reqList) {

									//	Add the request to the list

									m_reqList.addElement(nbReq);

									//	Wakeup the processing thread

									m_reqList.notify();
								}
							}
						}
					}
				}
				catch (Exception ex) {

					//	Debug

					if ( Debug.EnableError && hasDebug()) {
						Debug.println("NetBIOS Name refresh thread exception");
						Debug.println(ex);
					}
				}
			}
		}
	};

  /**
   * Default constructor
   *
   * @param config ServerConfiguration
   * @exception SocketException		If a network setup error occurs
   */
  public NetBIOSNameServer(ServerConfiguration config)
  	throws SocketException {

		super("NetBIOS", config);

		//	Perform common constructor code

		commonConstructor();
  }

	/**
	 * Common constructor code
	 *
   * @exception SocketException		If a network setup error occurs
	 */
	private final void commonConstructor()
		throws SocketException {

		//	Add the NetBIOS name server as a configuration change listener of the server configuration

		getConfiguration().addListener(this);

		//	Set the server version

		setVersion(ServerVersion);

    //  Find the CIFS server configuration

    m_cifsConfig = (CIFSConfigSection) getConfiguration().getConfigSection( CIFSConfigSection.SectionName);
    if ( m_cifsConfig != null) {

      //  Allocate the local and remote name tables

      m_localNames = new Vector<NetBIOSName>();
      m_remoteNames = new Hashtable<NetBIOSName, byte[]>();

  		//	Check if NetBIOS name server debug output is enabled

  		if ( getCIFSConfiguration().hasNetBIOSDebug())
  			setDebug(true);

  		//	Set the local address to bind the server to, and server port

  		setBindAddress(getCIFSConfiguration().getNetBIOSBindAddress());
  		setServerPort(getCIFSConfiguration().getNameServerPort());

  		//	Copy the WINS server addresses, if set

  		setPrimaryWINSServer(getCIFSConfiguration().getPrimaryWINSServer());
  		setSecondaryWINSServer(getCIFSConfiguration().getSecondaryWINSServer());

  		//	Check if WINS is not enabled, use broadcasts instead

  		if ( hasPrimaryWINSServer() == false) {

  			try {
  				m_bcastAddr = InetAddress.getByName(getCIFSConfiguration().getBroadcastMask());
  			}
  			catch (Exception ex) {
  			}
  		}
    }
    else
      setEnabled( false);
	}

  /**
   * Return the local address the server binds to, or null if all local addresses
   * are used.
   *
   * @return java.net.InetAddress
   */
  public final InetAddress getBindAddress() {
    return m_bindAddress;
  }

  /**
   * Return the next available transaction id for outgoing NetBIOS packets.
   *
   * @return int
   */
  protected final synchronized int getNextTransactionId() {
    return m_tranId++;
  }

  /**
   * Return the port/socket that the server is bound to.
   *
   * @return int
   */
  public final int getPort() {
    return m_port;
  }

  /**
   * Determine if the server binds to a particulat local address, or all addresses
   *
   * @return boolean
   */
  public final boolean hasBindAddress() {
    return m_bindAddress != null ? true : false;
  }

  /**
   * Return the CIFS server configuration
   *
   * @return CIFSConfigSection
   */
  private final CIFSConfigSection getCIFSConfiguration() {
    return m_cifsConfig;
  }

	/**
	 * Return the remote name table
	 *
	 * @return Hashtable
	 */
	public final Hashtable getNameTable() {
	  return m_remoteNames;
	}

	/**
	 * Return the broadcast address, if WINS is disabled
	 *
	 * @return InetAddress
	 */
	public final InetAddress getBroadcastAddress() {
		return m_bcastAddr;
	}

	/**
	 * Determine if the primary WINS server address has been set
	 *
	 * @return boolean
	 */
	public final boolean hasPrimaryWINSServer() {
		return m_winsPrimary != null ? true : false;
	}

	/**
	 * Return the primary WINS server address
	 *
	 * @return InetAddress
	 */
	public final InetAddress getPrimaryWINSServer() {
		return m_winsPrimary;
	}

	/**
	 * Determine if the secondary WINS server address has been set
	 *
	 * @return boolean
	 */
	public final boolean hasSecondaryWINSServer() {
		return m_winsSecondary != null ? true : false;
	}

	/**
	 * Return the secondary WINS server address
	 *
	 * @return InetAddress
	 */
	public final InetAddress getSecondaryWINSServer() {
		return m_winsSecondary;
	}

  /**
   * Add a NetBIOS name.
   *
   * @param name     NetBIOS name to be added
   * @exception java.io.IOException 	I/O error occurred.
   */
  public final synchronized void AddName(NetBIOSName name)
  	throws IOException {

    //  Check if the NetBIOS name socket has been initialized

    if (m_socket == null)
      throw new IOException("NetBIOS name socket not initialized");

		//	Create an add name request and add to the request list

		NetBIOSRequest nbReq = new NetBIOSRequest(NetBIOSRequest.AddName, name, getNextTransactionId());

		//	Set the retry interval

		if ( hasPrimaryWINSServer())
			nbReq.setRetryInterval(AddNameWINSInterval);
		else
			nbReq.setRetryInterval(AddNameInterval);

		//	Add the name to the local name list

		m_localNames.addElement(name);

		//	Queue the request

		synchronized (m_reqList) {

			//	Add the request to the list

			m_reqList.addElement(nbReq);

			//	Wakeup the processing thread

			m_reqList.notify();
		}
  }

	/**
	 * Delete a NetBIOS name.
	 *
	 * @param name     NetBIOS name to be deleted
	 * @exception java.io.IOException 	I/O error occurred.
	 */
	public final synchronized void DeleteName(NetBIOSName name)
		throws IOException {

		//  Check if the NetBIOS name socket has been initialized

		if (m_socket == null)
			throw new IOException("NetBIOS name socket not initialized");

		//	Create a delete name request and add to the request list

		NetBIOSRequest nbReq = new NetBIOSRequest(NetBIOSRequest.DeleteName, name, getNextTransactionId(), DeleteNameRetries);
		nbReq.setRetryInterval(DeleteNameInterval);

		synchronized (m_reqList) {

			//	Add the request to the list

			m_reqList.addElement(nbReq);

			//	Wakeup the processing thread

			m_reqList.notify();
		}
	}

  /**
   * Add a local add name listener to the NetBIOS name server.
   *
   * @param l AddNameListener
   */
  public final synchronized void addAddNameListener(AddNameListener l) {

    //  Check if the add name listener list is allocated

    if (m_addListeners == null)
      m_addListeners = new Vector<AddNameListener>();
    m_addListeners.add(l);
  }

  /**
   * Add a query name listener to the NetBIOS name server.
   *
   * @param l QueryNameListener
   */
  public final synchronized void addQueryListener(QueryNameListener l) {

    //  Check if the query name listener list is allocated

    if (m_queryListeners == null)
      m_queryListeners = new Vector<QueryNameListener>();
    m_queryListeners.add(l);
  }

  /**
   * Add a remote name listener to the NetBIOS name server.
   *
   * @param l RemoteNameListener
   */
  public final synchronized void addRemoteListener(RemoteNameListener l) {

    //  Check if the remote name listener list is allocated

    if (m_remoteListeners == null)
      m_remoteListeners = new Vector<RemoteNameListener>();
    m_remoteListeners.add(l);
  }

  /**
   * Trigger an add name event to all registered listeners.
   *
   * @param name NetBIOSName
   * @param sts int
   */
  protected final synchronized void fireAddNameEvent(NetBIOSName name, int sts) {

    //  Check if there are any listeners

    if (m_addListeners == null || m_addListeners.size() == 0)
      return;

    //  Create a NetBIOS name event

    NetBIOSNameEvent evt = new NetBIOSNameEvent(name, sts);

    //  Inform all registered listeners

    for (int i = 0; i < m_addListeners.size(); i++) {
      AddNameListener addListener = m_addListeners.elementAt(i);
      addListener.netbiosNameAdded(evt);
    }
  }

  /**
   * Trigger an query name event to all registered listeners.
   *
   * @param name NetBIOSName
   * @param addr InetAddress
   */
  protected final synchronized void fireQueryNameEvent(NetBIOSName name, InetAddress addr) {

    //  Check if there are any listeners

    if (m_queryListeners == null || m_queryListeners.size() == 0)
      return;

    //  Create a NetBIOS name event

    NetBIOSNameEvent evt = new NetBIOSNameEvent(name, NetBIOSNameEvent.QUERY_NAME);

    //  Inform all registered listeners

    for (int i = 0; i < m_queryListeners.size(); i++) {
      QueryNameListener queryListener = m_queryListeners.elementAt(i);
      queryListener.netbiosNameQuery(evt, addr);
    }
  }

  /**
   * Trigger a name register event to all registered listeners.
   *
   * @param name NetBIOSName
   * @param addr InetAddress
   */
  protected final synchronized void fireNameRegisterEvent(NetBIOSName name, InetAddress addr) {

    //  Check if there are any listeners

    if (m_remoteListeners == null || m_remoteListeners.size() == 0)
      return;

    //  Create a NetBIOS name event

    NetBIOSNameEvent evt = new NetBIOSNameEvent(name, NetBIOSNameEvent.REGISTER_NAME);

    //  Inform all registered listeners

    for (int i = 0; i < m_remoteListeners.size(); i++) {
      RemoteNameListener nameListener = m_remoteListeners.elementAt(i);
      nameListener.netbiosAddRemoteName(evt,addr);
    }
  }

  /**
   * Trigger a name release event to all registered listeners.
   *
   * @param name NetBIOSName
   * @param addr InetAddress
   */
  protected final synchronized void fireNameReleaseEvent(NetBIOSName name, InetAddress addr) {

    //  Check if there are any listeners

    if (m_remoteListeners == null || m_remoteListeners.size() == 0)
      return;

    //  Create a NetBIOS name event

    NetBIOSNameEvent evt = new NetBIOSNameEvent(name, NetBIOSNameEvent.REGISTER_NAME);

    //  Inform all registered listeners

    for (int i = 0; i < m_remoteListeners.size(); i++) {
      RemoteNameListener nameListener = m_remoteListeners.elementAt(i);
      nameListener.netbiosReleaseRemoteName(evt,addr);
    }
  }

  /**
   * Open the server socket
   *
   * @exception SocketException
   */
  private void openSocket()
  	throws java.net.SocketException {

    //	Check if the server should bind to a particular local address, or all addresses

    if (hasBindAddress())
      m_socket = new DatagramSocket(getPort(), m_bindAddress);
    else
      m_socket = new DatagramSocket(getPort());
  }

  /**
   * Process a NetBIOS name query.
   *
   * @param pkt NetBIOSPacket
   * @param fromAddr InetAddress
   * @param fromPort int
   */
  protected final void processNameQuery(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {

    //  Check that the name query packet is valid

    if (pkt.getQuestionCount() != 1)
      return;

    //  Get the name that is being queried

    String searchName = pkt.getQuestionName();
    char nameType = searchName.charAt(15);

    int len = 0;
    while (len <= 14 && searchName.charAt(len) != ' ' && searchName.charAt(len) != 0)
      len++;
    searchName = searchName.substring(0, len);

    //  Check if this is an adapter status request

    if ( searchName.equals( NetBIOSName.AdapterStatusName)) {

      // Process the adapter status request

      processAdapterStatus( pkt, fromAddr, fromPort);
      return;
    }

    //  Debug

    if (Debug.EnableInfo && hasDebug())
      Debug.println("%% Query name=" + searchName + ", type=" + NetBIOSName.TypeAsString(nameType) + ", len=" + len);

    //  Search for the name in the local name table

    Enumeration<NetBIOSName> enm = m_localNames.elements();
    NetBIOSName nbName = null;
    boolean foundName = false;

    while (enm.hasMoreElements() && foundName == false) {

      //  Get the current NetBIOS name item from the local name table

      nbName = enm.nextElement();

      //  Debug

      if (Debug.EnableInfo && hasDebug())
        Debug.println("NetBIOS Name - " + nbName.getName() + ", len=" + nbName.getName().length() + ",type=" + NetBIOSName.TypeAsString(nbName.getType()));

      //  Check if the name matches the query name

      if (nbName.getType() == nameType && nbName.getName().compareTo(searchName) == 0)
        foundName = true;
    }

    //  Check if we found a matching name

    if (foundName == true) {

      //  Debug

      if (Debug.EnableInfo && hasDebug())
        Debug.println("%% Found name " + searchName + " in local name table : " + nbName.toString());

      //  Build the name query response

      int pktLen = pkt.buildNameQueryResponse(nbName);

      //  Debug

      if (Debug.EnableInfo && hasDebug()) {
        Debug.println("%% NetBIOS Reply to " + fromAddr.getHostAddress() + " :-");
        pkt.DumpPacket(false);
      }

      //  Send the reply packet

      try {

        //  Send the name query reply

        sendPacket(pkt, pktLen, fromAddr, fromPort);
      }
      catch (java.io.IOException ex) {
      	Debug.println(ex);
      }

      //  Inform listeners of the name query

      fireQueryNameEvent(nbName, fromAddr);
    }
    else {

      //  Debug

      if (Debug.EnableInfo && hasDebug())
        Debug.println("%% Failed to find match for name " + searchName);
    }
  }

  /**
   * Process a NetBIOS name register request.
   *
   * @param pkt NetBIOSPacket
   * @param fromAddr InetAddress
   * @param fromPort int
   */
  protected final void processNameRegister(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {

    //  Check that the name register packet is valid

    if (pkt.getQuestionCount() != 1)
      return;

    //  Get the name that is being registered

    String regName = pkt.getQuestionName();
    char nameType = regName.charAt(15);

    int len = 0;
    while (len <= 14 && regName.charAt(len) != ' ')
      len++;
    regName = regName.substring(0, len);

    //  Debug

    if (Debug.EnableInfo && hasDebug())
      Debug.println("%% Register name=" + regName + ", type=" + NetBIOSName.TypeAsString(nameType) + ", len=" + len);

		//	Create a NetBIOS name for the host

		byte[] hostIP = fromAddr.getAddress();
		NetBIOSName nbName = new NetBIOSName(regName, nameType, false, hostIP);

		//	Add the name to the remote host name table

		m_remoteNames.put(nbName, hostIP);

		//	Inform listeners that a new remote name has been added

		fireNameRegisterEvent(nbName,fromAddr);

		//	Debug

		if ( Debug.EnableInfo && hasDebug())
			Debug.println("%% Added remote name " + nbName.toString() + " to remote names table");
  }

  /**
   * Process a NetBIOS name release.
   *
   * @param pkt NetBIOSPacket
   * @param fromAddr InetAddress
   * @param fromPort int
   */
  protected final void processNameRelease(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {

    //  Check that the name release packet is valid

    if (pkt.getQuestionCount() != 1)
      return;

    //  Get the name that is being released

    String regName = pkt.getQuestionName();
    char nameType = regName.charAt(15);

    int len = 0;
    while (len <= 14 && regName.charAt(len) != ' ')
      len++;
    regName = regName.substring(0, len);

    //  Debug

    if (Debug.EnableInfo && hasDebug())
      Debug.println("%% Release name=" + regName + ", type=" + NetBIOSName.TypeAsString(nameType) + ", len=" + len);

		//	Create a NetBIOS name for the host

		byte[] hostIP = fromAddr.getAddress();
		NetBIOSName nbName = new NetBIOSName(regName, nameType, false, hostIP);

		//	Remove the name from the remote host name table

		m_remoteNames.remove(nbName);

		//	Inform listeners that a remote name has been released

		fireNameReleaseEvent(nbName, fromAddr);

		//	Debug

		if ( Debug.EnableInfo && hasDebug())
			Debug.println("%% Released remote name " + nbName.toString() + " from remote names table");
  }

  /**
   * Process a NetBIOS query response.
   *
   * @param pkt NetBIOSPacket
   * @param fromAddr InetAddress
   * @param fromPort int
   */
  protected final void processQueryResponse(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
  }

  /**
   * Process a NetBIOS name register response.
   *
   * @param pkt NetBIOSPacket
   * @param fromAddr InetAddress
   * @param fromPort int
   */
  protected final void processRegisterResponse(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {

		//	Check if there are any reply name details

		if ( pkt.getAnswerCount() == 0)
			return;

  	//	Get the details from the response packet

  	int tranId = pkt.getTransactionId();

		//	Find the matching request

		NetBIOSRequest req = findRequest(tranId);
		if ( req == null)
			return;

		//	Get the error code from the response

		int errCode = pkt.getResultCode();

		if ( errCode != 0) {

			//	Mark the request error

			req.setErrorStatus(true);

			//	Get the name details

	    String regName = pkt.getAnswerName();
	    char nameType = regName.charAt(15);

	    int len = 0;
	    while (len <= 14 && regName.charAt(len) != ' ')
	      len++;
	    regName = regName.substring(0, len);

			//	Create a NetBIOS name for the host

			byte[] hostIP = fromAddr.getAddress();
			NetBIOSName nbName = new NetBIOSName(regName, nameType, false, hostIP);

	    //  Debug

	    if (Debug.EnableInfo && hasDebug())
	      Debug.println("%% Negative Name Registration name=" + nbName);

			//	Inform listeners of the add name failure

			fireAddNameEvent(req.getNetBIOSName(), NetBIOSNameEvent.ADD_FAILED);
		}
		else {

			//  Debug

			if (Debug.EnableInfo && hasDebug())
				Debug.println("%% Name Registration Successful name=" + req.getNetBIOSName().getName());

			//	Inform listeners that the add name was successful

			fireAddNameEvent(req.getNetBIOSName(), NetBIOSNameEvent.ADD_SUCCESS);
		}
  }

  /**
   * Process a NetBIOS name release response.
   *
   * @param pkt NetBIOSPacket
   * @param fromAddr InetAddress
   * @param fromPort int
   */
  protected final void processReleaseResponse(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
  }

  /**
   * Process a NetBIOS WACK.
   *
   * @param pkt NetBIOSPacket
   * @param fromAddr InetAddress
   * @param fromPort int
   */
  protected final void processWack(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
  }

  /**
   * Process an adapter status request
   * @param pkt NetBIOSPacket
   * @param fromAddr InetAddress
   * @param fromPort int
   */
  protected final void processAdapterStatus(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {

    //  Debug

    if (Debug.EnableInfo && hasDebug())
      Debug.println("%% Adapter status request");

    //  Build the local name list

    NetBIOSNameList nameList = new NetBIOSNameList();
    for ( int i = 0; i < m_localNames.size(); i++)
      nameList.addName( m_localNames.get( i));

    //  Build the name query response

    int pktLen = pkt.buildAdapterStatusResponse( nameList, hasPrimaryWINSServer() ? NetBIOSPacket.NAME_TYPE_PNODE : NetBIOSPacket.NAME_TYPE_BNODE);

    //  Debug

    if (Debug.EnableInfo && hasDebug()) {
      Debug.println("%% NetBIOS Reply to " + fromAddr.getHostAddress() + " :-");
      pkt.DumpPacket(false);
    }

    //  Send the reply packet

    try {

      //  Send the name query reply

      sendPacket(pkt, pktLen, fromAddr, fromPort);
    }
    catch (java.io.IOException ex) {
      Debug.println(ex);
    }
  }

  /**
   * Remove a local add name listener from the NetBIOS name server.
   *
   * @param l AddNameListener
   */
  public final synchronized void removeAddNameListener(AddNameListener l) {

    //  Check if the listener list is valid

    if (m_addListeners == null)
      return;
    m_addListeners.removeElement(l);
  }

  /**
   * Remove a query name listner from the NetBIOS name server.
   *
   * @param l QueryNameListener
   */
  public final synchronized void removeQueryNameListener(QueryNameListener l) {

    //  Check if the listener list is valid

    if (m_queryListeners == null)
      return;
    m_queryListeners.removeElement(l);
  }

  /**
   * Remove a remote name listener from the NetBIOS name server.
   *
   * @param l RemoteNameListener
   */
  public final synchronized void removeRemoteListener(RemoteNameListener l) {

    //  Check if the listener list is valid

    if (m_remoteListeners == null)
      return;
    m_remoteListeners.removeElement(l);
  }

  /**
   * Run the NetBIOS name server.
   */
  public void run() {

    //  Initialize the NetBIOS name socket

		NetBIOSPacket nbPkt = null;
		DatagramPacket pkt = null;
		byte[] buf = null;

    try {

      //  Get a list of the local IP addresses

			Vector ipList = new Vector();

			if ( hasBindAddress()) {

				//	Use the specified bind address

				ipList.addElement(getBindAddress().getAddress());
			}
			else {

				//	Get a list of all the local addresses

				InetAddress[] addrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());

				for ( int i = 0; i < addrs.length; i++) {

					//	Check for a valid address, filter out '127.0.0.1' and '0.0.0.0' addresses

					if ( addrs[i].getHostAddress().equals("127.0.0.1") == false &&
							 addrs[i].getHostAddress().equals("0.0.0.0") == false)
						ipList.addElement(addrs[i].getAddress());
				}

        // If no valid addresses were found use the network interface list to find the local server address(es)

        if ( ipList.size() == 0) {

          // Enumerate the network adapter list

          Enumeration niEnum = NetworkInterface.getNetworkInterfaces();

          if ( niEnum != null) {

            while ( niEnum.hasMoreElements()) {

              // Get the current network interface

              NetworkInterface ni = (NetworkInterface) niEnum.nextElement();

              // Enumerate the addresses for the network adapter

              Enumeration niAddrs = ni.getInetAddresses();
              if ( niAddrs != null)
              {
                // Check for any valid addresses

                while ( niAddrs.hasMoreElements())
                {
                  InetAddress curAddr = (InetAddress) niAddrs.nextElement();

                  if ( curAddr.getHostAddress().equals("127.0.0.1") == false &&
                      curAddr.getHostAddress().equals("0.0.0.0") == false)
                    ipList.add( curAddr.getAddress());
                }
              }
            }

            // DEBUG

            if ( Debug.EnableInfo && ipList.size() > 0 && hasDebug())
              Debug.println("Found " + ipList.size() + " addresses using interface list");
          }
        }
			}

      //  Initialize the NetBIOS name socket

      if (m_socket == null)
        openSocket();

			//	Allocate the NetBIOS request queue, and add the server name/alias name requests

			m_reqList = new Vector<NetBIOSRequest>();

			//	Add the server name requests to the queue

			AddName(new NetBIOSName(m_cifsConfig.getServerName(), NetBIOSName.FileServer, false, ipList, DefaultTTL));
			AddName(new NetBIOSName(m_cifsConfig.getServerName(), NetBIOSName.WorkStation, false, ipList, DefaultTTL));

			if ( getCIFSConfiguration().getDomainName() != null)
				AddName(new NetBIOSName(m_cifsConfig.getDomainName(), NetBIOSName.Domain, true, ipList, DefaultTTL));

			//	Check if the server has alias names configured, if so then also add those names

			if ( getCIFSConfiguration().hasAliasNames()) {

				//	Add the alias names to the network

				StringList names = getCIFSConfiguration().getAliasNames();

				for ( int i = 0; i < names.numberOfStrings(); i++) {

					//	Get the current alias name

					String alias = (String) names.getStringAt(i);

					//	Add the name to the network

					AddName(new NetBIOSName(alias, NetBIOSName.FileServer, false, ipList, DefaultTTL));
					AddName(new NetBIOSName(alias, NetBIOSName.WorkStation, false, ipList, DefaultTTL));
				}
			}

			//	Create the request handler thread

			m_reqHandler = new NetBIOSRequestHandler();
			m_reqHandler.start();

			//	Create the name refresh thread

			m_refreshThread = new NetBIOSNameRefresh();
			m_refreshThread.start();

      //  Allocate a receive buffer, NetBIOS packet and datagram packet

      buf = new byte[1024];
      nbPkt = new NetBIOSPacket(buf);
      pkt = new DatagramPacket(buf, buf.length);
    }
    catch ( Exception ex) {
    	if ( Debug.EnableError && hasDebug())
    		Debug.println("NetBIOSNameServer setup error:" + ex.toString());

    	//	Save the exception and inform listeners of the error

    	setException(ex);
    	fireServerEvent(ServerListener.ServerError);
    }

    //	If there are any pending requests in the queue then wakeup the request handler thread

    if ( m_reqList != null && m_reqList.size() > 0) {
    	synchronized ( m_reqList) {
    		m_reqList.notify();
    	}
    }

		//	Indicate that the server is active

		setActive(true);
		fireServerEvent(ServerListener.ServerActive);

    //  Loop

		if ( hasException() == false) {

			//	Clear the shutdown request flag

	    m_shutdown = false;

	    while (m_shutdown == false) {

				try {

	        //  Wait for an incoming packet ....

	        m_socket.receive(pkt);

	        //  Debug

	        if (Debug.EnableInfo && hasDebug()) {

	          //  Dump the received packet

	          Debug.println("%% NetBIOS Name Server Rx Datagram from " + pkt.getAddress().getHostAddress() + ", opCode=" + nbPkt.getOpcode());

	          //  Dump the raw packet details

	          HexDump.Dump(buf, pkt.getLength(), 0, Debug.getDebugInterface());
	        }

					//	Check for a zero length datagram

					if ( pkt.getLength() == 0)
						continue;

	        //  Get the incoming NetBIOS packet opcode

	        InetAddress fromAddr = pkt.getAddress();
	        int fromPort = pkt.getPort();

	        switch (nbPkt.getOpcode()) {

	          //  Name query

	          case NetBIOSPacket.NAME_QUERY :
	            processNameQuery(nbPkt, fromAddr, fromPort);
	            break;

	          //  Name register

	          case NetBIOSPacket.NAME_REGISTER :
	            processNameRegister(nbPkt, fromAddr, fromPort);
	            break;

	          //  Name release

	          case NetBIOSPacket.NAME_RELEASE :
	            processNameRelease(nbPkt, fromAddr, fromPort);
	            break;

	          //  Name register response

	          case NetBIOSPacket.RESP_REGISTER :
	            processRegisterResponse(nbPkt, fromAddr, fromPort);
	            break;

	          //  Name query response

	          case NetBIOSPacket.RESP_QUERY :
	            processQueryResponse(nbPkt, fromAddr, fromPort);
	            break;

	          //  Name release response

	          case NetBIOSPacket.RESP_RELEASE :
	            processReleaseResponse(nbPkt, fromAddr, fromPort);
	            break;

	          //  WACK

	          case NetBIOSPacket.WACK :
	            processWack(nbPkt, fromAddr, fromPort);
	            break;

            // Refresh

            case NetBIOSPacket.REFRESH:
              processNameRegister(nbPkt, fromAddr, fromPort);
              break;

            // Multi-homed name registration

            case NetBIOSPacket.NAME_REGISTER_MULTI:
              processNameRegister(nbPkt, fromAddr, fromPort);
              break;

	          //	Unknown opcode

	          default:
	          	if ( Debug.EnableError && hasDebug()) {
	          	  int opCode = nbPkt.getOpcode();
	          		Debug.println("Unknown OpCode 0x" + Integer.toHexString(opCode) + ", " + opCode);
	          	}
	          	break;
	        }
	    	}
	    	catch (Exception ex) {
	    		if ( hasDebug()) {
	    			Debug.println("NetBIOSNameServer error");    			Debug.println(ex);
	    		}

	    		//	Store the error and inform listeners of the server error. If the server is shutting down we expect a
	    		//	socket error as the socket is closed by the shutdown thread and the pending read request generates an
	    		//	exception.

	    		if ( m_shutdown == false) {
		    		setException(ex);
		    		fireServerEvent(ServerListener.ServerError);
					}
	    	}
	    }
		}

		//	Indicate that the server is closed

		setActive(false);
		fireServerEvent(ServerListener.ServerShutdown);
  }

  /**
   * Send a packet via the NetBIOS naming datagram socket.
   *
   * @param nbpkt NetBIOSPacket
   * @param len int
   * @exception java.io.IOException The exception description.
   */
  protected final void sendPacket(NetBIOSPacket nbpkt, int len)
  	throws java.io.IOException {

    //  Allocate the datagram packet, using the add name buffer

    DatagramPacket pkt = new DatagramPacket(nbpkt.getBuffer(), len, NetworkSettings.getBroadcastAddress(), getPort());

    //  Send the datagram packet

    m_socket.send(pkt);
  }

	/**
	 * Send a packet via the NetBIOS naming datagram socket.
	 *
	 * @param nbpkt NetBIOSPacket
	 * @param len int
	 * @param replyAddr InetAddress
	 * @param replyPort int
	 * @exception java.io.IOException The exception description.
	 */
	protected final void sendPacket(NetBIOSPacket nbpkt, int len, InetAddress replyAddr, int replyPort)
		throws java.io.IOException {

		//  Allocate the datagram packet, using the add name buffer

		DatagramPacket pkt = new DatagramPacket(nbpkt.getBuffer(), len, replyAddr, replyPort);

		//  Send the datagram packet

		m_socket.send(pkt);
	}

  /**
   * Set the local address that the server should bind to
   *
   * @param addr java.net.InetAddress
   */
  public final void setBindAddress(InetAddress addr) {
    m_bindAddress = addr;
  }

	/**
	 * Set the server port
	 *
	 * @param port int
	 */
	public final void setServerPort(int port) {
		m_port = port;
	}

	/**
	 * Set the primary WINS server address
	 *
	 * @param addr InetAddress
	 */
	public final void setPrimaryWINSServer(InetAddress addr) {
		m_winsPrimary = addr;
	}

	/**
	 * Set the secondary WINS server address
	 *
	 * @param addr InetAddress
	 */
	public final void setSecondaryWINSServer(InetAddress addr) {
		m_winsSecondary = addr;
	}

	/**
	 * Find the NetBIOS request with the specified transation id
	 *
	 * @param id int
	 * @return NetBIOSRequest
	 */
	private final NetBIOSRequest findRequest(int id) {

		//	Check if the request list is valid

		if ( m_reqList == null)
			return null;

		//	Need to lock access to the request list

		NetBIOSRequest req = null;

		synchronized ( m_reqList) {

			//	Search for the required request

			int idx = 0;

			while ( req == null && idx < m_reqList.size()) {

				//	Get the current request and check if it is the required request

				NetBIOSRequest curReq = (NetBIOSRequest) m_reqList.elementAt(idx++);
				if ( curReq.getTransactionId() == id)
					req = curReq;
			}
		}

		//	Return the request, or null if not found

		return req;
	}

	/**
	 * Shutdown the NetBIOS name server
	 *
	 * @param immediate boolean
	 */
	public void shutdownServer(boolean immediate) {

		//	Close the name refresh thread

		try {

			if ( m_refreshThread != null) {
				m_refreshThread.shutdownRequest();
			}
		}
		catch (Exception ex) {

			//	Debug

			if ( Debug.EnableError)
				Debug.println(ex);
		}

		//	If the shutdown is not immediate then release all of the names registered by this server

		if ( isActive() && immediate == false) {

			//	Release all local names

			for ( int i = 0; i < m_localNames.size(); i++) {

				//	Get the current name details

				NetBIOSName nbName = (NetBIOSName) m_localNames.elementAt(i);

				//	Queue a delete name request

				try {
					DeleteName(nbName);
				}
				catch (IOException ex) {
					Debug.println(ex);
				}
			}

			//	Wait for the request handler thread to process the delete name requests

			while ( m_reqList.size() > 0) {
				try {
					Thread.sleep(100);
				}
				catch (InterruptedException ex) {
				}
			}
		}

		//	Close the request handler thread

		try {

			//	Close the request handler thread

			if (m_reqHandler != null) {
				m_reqHandler.shutdownRequest();
				m_reqHandler.join(1000);
				m_reqHandler = null;
			}
		}
		catch (Exception ex) {

			//	Debug

			if ( Debug.EnableError)
				Debug.println(ex);
		}

		//	Indicate that the server is closing

		m_shutdown = true;

		try {


			//	Close the server socket so that any pending receive is cancelled

			if (m_socket != null) {

				//	Send a dummy packet to release the receive on the datagram socket, then close the socket

//				m_socket.send(new DatagramPacket(new byte[0], 0, m_socket.getLocalAddress(), getPort()));
				try {
					m_socket.close();
				}
				catch (Exception ex) {
				}
				m_socket = null;
			}
		}
		catch (Exception ex) {
			Debug.println(ex);
		}

    //	Fire a shutdown notification event

    fireServerEvent(ServerListener.ServerShutdown);
	}

	/**
	 * Start the NetBIOS name server is a seperate thread
	 */
	public void startServer() {

		//	Create a seperate thread to run the NetBIOS name server

		m_srvThread = new Thread(this);
		m_srvThread.setName("NetBIOS Name Server");
		m_srvThread.setDaemon(true);

		m_srvThread.start();

		//	Fire a server startup event

		fireServerEvent(ServerListener.ServerStartup);
	}


	/**
	 *	Validate configuration changes that are relevant to the NetBIOS name server
	 *
	 * @param id int
	 * @param config ServerConfiguration
	 * @param newVal Object
	 * @return int
	 * @throws InvalidConfigurationException
	 */
	public int configurationChanged(int id, ServerConfiguration config, Object newVal)
		throws InvalidConfigurationException {

		int sts = StsIgnored;

		try {

			//	Check if the configuration change affects the NetBIOS name server

			switch ( id) {

				//	Server enable/disable

				case ConfigId.SMBNetBIOSEnable:

					//	Check if the server is active

					Boolean enaSMB = (Boolean) newVal;

					if ( isActive() && enaSMB.booleanValue() == false) {

						//	Shutdown the server

						shutdownServer(false);
					}
					else if ( isActive() == false && enaSMB.booleanValue() == true) {

						//	Start the server

						startServer();
					}

					//	Indicate that the setting was accepted

					sts = StsAccepted;
					break;

				//	Debug enable

				case ConfigId.NetBIOSDebugEnable:

					//	Toggle the debug setting

					Boolean b = (Boolean) newVal;
					setDebug(b.booleanValue());

					sts = StsAccepted;
					break;

				//	Settings that require a restart

				case ConfigId.NetBIOSNamePort:
				case ConfigId.NetBIOSSessionPort:
				case ConfigId.NetBIOSBindAddress:
				case ConfigId.SMBBroadcastMask:
					sts = StsRestartRequired;
					break;
			}
		}
		catch (Exception ex) {
			throw new InvalidConfigurationException("NetBIOS Server configuration error", ex);
		}

		//	Return the status

		return sts;
	}
}
